#!/usr/bin/env python

# pyplusplus script
#
import sys, re, os, os.path, time, logging, glob, copy
import optparse
import distutils.sysconfig
#import goodies.goodie_perf_overrides
from goodies import *

import pygccxml

try:
   import xml.etree.ElementTree as et
except:
   import elementtree.ElementTree as et

import xml.parsers.expat

import settings, core_info

pj = os.path.join

# TODO:
# - Implement finalize  (needs testing)
# - Ensure objects are stored as RefPtrs and auto converted to RefPtrs
# - Wrap begin/end edit
#
timing = [("start",time.time()),]
use_profiler = False
use_query_opt = True

# Setup global settings
set_recursive_default(False)
sys.setrecursionlimit( sys.getrecursionlimit() * 10)   # Need a little more room for bindings
set_logger_level(logging.INFO)
#set_logger_level(logging.DEBUG)
import pyplusplus.code_creators.calldef
pyplusplus.code_creators.calldef.use_enum_workaround = True
import pyplusplus.function_transformers as FT


# ---------------------------------------------------------- #
#           Helper methods and Classes                       #
# ---------------------------------------------------------- #
def cleanTemplateName(templateName):
   """ Build a clean template name. 
       This is needed when we need a symbol name for code but only have a template name.
   """
   clean_re = re.compile('[\-:<>\s,]')
   return clean_re.sub('_', templateName)

class LocalTemplateBuilder:
   """ Template builder helper class.
       This class is meant to simplify the use of templates with py++.
       
       It is a bit hairy, but the concept is that we try to:
       - Get some generated code into a file that will cause        
           the template to instantiate
       - Create a typedef that we can use later to lookup the template instance
       
       XXX: Why do we need this instead of goodie_utils.TemplateBuilder???
   """   
   def __init__(self):
      """ Initialize template builder. """
      # List of tuples: (templateTypename, typedefName)
      #       ex: ("MyClass<float>","myclass_float")
      self.mTemplates = []
   
   def Template(self, templateType, typedefName=None):
      """Add a template to instantiate.
      
      @param templateType: Fully instantiated name.  (ex: TemplateClass<float> )
      @param typedefName:  Name of type def to define to this template type.
          This name can then be used later to look up the real fully
          expanded template type from the typedef map.
      """
      if not typedefName:
         typedefName = cleanTemplateName(templateType)
      self.mTemplates.append( (templateType, typedefName) )

   def buildTemplateFileContents(self):
      """ Build up the contents of a file to instantiate the needed templates.
          headerFiles - List of header files for this module.
      """
      if len(self.mTemplates) == 0:
         return None
      
      content = "/** Autogenerated temporary file for template instantiation. */\n"
      for t in self.mTemplates:
         template_type = t[0]
         typedef_name = t[1]
         content += """
            typedef %(template_type)s %(typedef_name)s;
            inline unsigned __instantiate_%(typedef_name)s()
            { return unsigned(sizeof(%(typedef_name)s)); }
         """ % vars()      
      
      return content   
   
   
def buildContainerHeader(headers, extraContent="/** Nothing Extra */", filename=None):
   """ Build up a header containing a bunch of other headers.
       headers - List of header files for this module.
       returns a temp file or a file created with filename.
   """
   if filename == None:
      filename = pygccxml.utils.create_temp_file_name(suffix=".h")   
   content = "/** Auto-generated temporary file for header containment. */\n"
   for h in headers:
      content += '#include <%s>\n'%h
   content += '\n\n'
   content += extraContent
   content += '\n\n'
   
   temp_file = file(filename, 'w')
   temp_file.write(content)
   temp_file.close()
      
   return filename

class FCPolicyResolver(object):
   """ Call policy resolver for field containers and OpenSG classes.
       Based on default_policy_resolver and return_value_policy_resolver
       Trys to do as much automatically as possible to wrap OpenSG methods.
       
       Take a look at default_policy_resolver and return_value_policy_resolver
       for inspiration, details, and ideas to make this better.
   """
   def __init__(self):      
      self.__const_char_pointer = pointer_t(const_t(char_t()))
      self.__const_wchar_pointer = pointer_t(const_t(wchar_t()))
      self.getSF_re  = re.compile('getSF.*')
      self.getMF_re  = re.compile('getMF.*')
      self.get_re    = re.compile('get.*')
      self.edit_re   = re.compile('edit.*')   
      self.create_re = re.compile('create.*')      
      
   def __call__(self,calldef, hint=None):
      if not isinstance( calldef, calldef_t ):
         return None
      
      if isinstance(calldef, constructor_t):
         return None

      # Build up helper variables
      ret_type      = remove_alias( calldef.return_type )
      naked_type    = remove_cv(remove_reference(ret_type))
      rt_is_const   = is_const(ret_type) or (hasattr(ret_type,'base') and is_const(ret_type.base))
      rt_is_ref     = is_reference(ret_type) or (hasattr(ret_type,'base') and is_reference(ret_type.base))
      rt_is_pointer = is_pointer(ret_type)
      rt_is_class   = is_class(naked_type) 
      rt_is_fundamental = isinstance(naked_type, fundamental_t)     
      
      #print "method: [%s] is_ref:%s is_const:%s is_pointer:%s is_class:%s"%\
      #        (calldef.name,rt_is_ref,rt_is_const, rt_is_pointer,rt_is_class)
      #print_declarations(calldef,detailed=True,recursive=True)

      # Custom methods
      if calldef.name in ["getType", "getClass", "getStaticClass"]:
         return return_internal_reference()

      # getClassType() is a static method that returns a reference to an
      # existing object. Because it is a static method, it cannot use
      # return_internal_refernce because there is no custodian object.
      if calldef.name in ["getClassType"]:
         return return_value_policy(reference_existing_object)

      # Creation methods return new objects
      # - Only something special when returning ptr, smart_ptr handled already
      if self.create_re.match(calldef.name) and rt_is_pointer:
         return return_value_policy(manage_new_object)
      
      # Handle singleton method
      if "the" == calldef.name:
         return return_value_policy(reference_existing_object)
      
      # Check for default cases of:
      # - const_char_pointer, wchar ptr
      # - fundamental type (ref and non ref
      if is_same( naked_type, self.__const_char_pointer ):
         return default_call_policies()
      if is_same( ret_type, self.__const_wchar_pointer ):
         return return_value_policy( return_by_value )
      if rt_is_fundamental:
         if rt_is_ref:
            return return_value_policy(return_by_value)
         else:
            return default_call_policies()
      
      #if isinstance( ret_type, declarations.reference_t ) \
      #   and isinstance( ret_type.base, declarations.const_t ):
      if rt_is_const and rt_is_ref and rt_is_class:
         return return_value_policy( copy_const_reference )
      
      # Check gets, edits, and [] for things returning internal references
      # Note: Why not just do this on all return types?  Well because we don't
      #       know for sure that we can do this.  It is method specific and we are just
      #       making assumptions based on naming conventions in OpenSG
      if self.get_re.match(calldef.name) or self.edit_re.match(calldef.name) or \
            (hasattr(calldef,'symbol') and calldef.symbol == '[]'):
         if rt_is_ref:
            if is_fundamental( naked_type ) or is_enum( naked_type ):
               if is_const:
                  return return_value_policy(copy_const_reference)
               else:
                  return return_value_policy(copy_non_const_reference)
            elif rt_is_const:
               return return_value_policy(copy_const_reference)
            else:
               return return_internal_reference()
         elif rt_is_pointer:
            if rt_is_fundamental:
               assert false, "should not get here"
               return default_call_policies()
            elif rt_is_const:
               return default_call_policies()
            else:
               return return_internal_reference()
         
      # Handle Image:: returning const *. For now try this in general
      if rt_is_const and rt_is_pointer:
         return return_internal_reference()
      
      return None

# The FCPolicyResolver instance to use
fc_policy_resolver = FCPolicyResolver()

def handleClassDefaults(cls):   
   """ Perform default processing on the given class.
       This includes just about everything we can do by
       default to change methods exposed, call policies, etc.
   """   
   exclude_protected(cls)
   wrap_const_ref_params(cls)
   
   # Hide all Mask variables
   # - these don't expose well and osg2 will not have them anyway
   cls.variables(regex_matcher(".*Mask"), allow_empty=True).exclude()
   cls.variables(allow_empty=True).exclude()
   cls.variables(regex_matcher("stat.*"), allow_empty=True).include()
   cls.variables(regex_matcher("stat.*"), allow_empty=True).is_read_only = True
   
   # For now ignore all the field based access and the edit based ref access
   cls.member_functions(regex_matcher("edit.*"),allow_empty=True).exclude() 
   cls.member_functions(regex_matcher("(get|edit)(MF|SF).*"),allow_empty=True).exclude()   

   # Remove a set of methods and parameters that we have deemed to be
   # "bad", (ie. difficult or impossible to wrap)
   common_exclude = ["isNodeCore","newPtr","editSField","editMField",
                     "pushToField","insertIntoMField","replaceInMField",
                     "replaceInMField","removeFromMField","removeFromMField",
                     "clearField", 
                     "registerChangedContainer","registerChangedContainerV",
                     "execSync","execSyncV","deregister",
                     "getData","editData"]
   # Methods that we don't want to expose for now because we can't or don't use them
   dont_expose = ["shallowCopy", "draw", "activate", "deactivate", "changeFrom"]
   for p in common_exclude + dont_expose:
      cls.decls(p,allow_empty=True).exclude()
        
   
   # Build up a list of unique method names from the class
   all_calldefs = cls.calldefs()
   contained_names = set()
   for c in all_calldefs:
      contained_names.add(c.name)   
   
   for n in contained_names:
      #print "  checking calldef: [%s]"%n,
      methods = cls.calldefs(n)
      #print "  found num: ", len(methods)

      # Resolve the call policies on any methods left
      for m in methods:
         # Skip the method if it is ignored
         if True == m.ignore:
            continue
         
         # These operations on chunks, materials, images cause problems
         # with boost.python because of protected destructor
         bad_ops = ["<","==","!="] 
         bad_ops.extend( ["->",] )   # We just don't want these ops because they don't help
         if isinstance(m, operator_t) and m.symbol in bad_ops:
            m.exclude()
            continue
         
         m.call_policies = fc_policy_resolver(m)
   
   # Overrides
   retMF_re = re.compile('.*MF.*')
   for c in all_calldefs:
      ret_type = remove_alias( c.return_type )
      ret_naked_type = remove_cv(remove_reference(ret_type))
      rt_is_const = is_const(ret_type) or (hasattr(ret_type,'base') and is_const(ret_type.base))
      rt_is_ref = is_reference(ret_type) or (hasattr(ret_type,'base') and is_reference(ret_type.base))
      rt_is_pointer = is_pointer(ret_type)
      rt_is_class = is_class(ret_naked_type)      
      rt_is_mf_type = rt_is_class and retMF_re.match(ret_naked_type.declaration.name)
   
      if not c.ignore and rt_is_mf_type and rt_is_const:
         # Write custom wrapper for the return value
         addMFRetListWrapper(c, ret_naked_type)


def addMFRetListWrapper(calldef, nakedRetType):
   """ Try to add a custom wrapper for methods that return data through an MField. """
   class_decl = calldef.parent
   classname = class_decl.name
   fullclassname = class_decl.decl_string.lstrip(":")
   methodname = calldef.name

   # We use the actual return type from the method being invoked rather than
   # the "naked" return type. In general, this avoids copying the MField
   # object, and it allows us to use the typedef'd MField name quite easily.
   # This is important for portability reasons (in particular because GLenum,
   # as used by OSG::MFGLenum) is defined differently on different platforms.
   mftype = calldef.return_type.decl_string.lstrip(":")

   new_methodname = "%s_%s"%(cleanTemplateName(classname),methodname)   # calldef.location.line
   
   code = """
   boost::python::list %(new_methodname)s(%(fullclassname)s *self)
   {
      boost::python::list result;
      %(mftype)s mf_data = self->%(methodname)s();
      for(unsigned i=0;i<mf_data.size();++i)
      { result.append(mf_data[i]); }
      return result;
   }
   """ % vars()
   
   class_decl.add_declaration_code(code)
   calldef.ignore = True
   #print "Adding MFRetList wrapper for: %s %s"%(classname,methodname)
   add_member_function(class_decl, methodname, new_methodname)   
   
   
def addFCPtrCode(c, mb, className, parentName=None):
   """ For a given class, try to add all the field container pointer handling
       code we need to wrap.
       c  - The class
       mb - Our module builder
       className  - The name of the class we are dealing with.
       parentName - The name of our parent class if we have one
   """
   # If we are noncopyable, then we can't set a held type
   # but we still need to register the pointer so python knows about us
   ptr_str            = "%s::ObjPtr"%className
   ref_ptr_str        = "OSG::RefPtr< %s::ObjPtr >"%className
   ref_ptr_parent_str = "OSG::RefPtr< %s::ObjPtr >"%parentName
   if not c.is_abstract:
      c.held_type = ref_ptr_str
   else:
      #print "XXXXXXXXXXXXXX: Abstract: %s XXXXXXXXXXXXXXXXXXXXXXXXXXX"%className
      c.add_registration_code("bp::register_ptr_to_python< %s >();"%ref_ptr_str, works_on_instance=False)
   
   # Add on the ptr conversion methods needed
   #c.add_registration_code("bp::register_ptr_to_python< OSG::%sPtr >();"%cinfo.name, works_on_instance=False)
   c.add_registration_code("pyopensg::register_fcptr< %s >::execute();"%ref_ptr_str, works_on_instance=False)
   c.add_registration_code("bp::implicitly_convertible< %s, %s >();"%(ref_ptr_str, ptr_str), works_on_instance=False)
   if parentName:
      #c.add_registration_code("bp::implicitly_convertible< OSG::%sPtr, OSG::%sPtr >();"%(className,parentNamet), works_on_instance=False)   
      c.add_registration_code("bp::implicitly_convertible< %s, %s >();"%(ref_ptr_str,ref_ptr_parent_str), works_on_instance=False)      
   mb.add_registration_code("/** Helpers for: %s */"%className)
   mb.add_registration_code('bp::def("RefPtr", &pyopensg::ToRefPtr<%s>);'%ptr_str)
   mb.add_registration_code('bp::def("FCPtr",  &pyopensg::ToFcPtr <%s>);'%ptr_str)

def exposeTemplateInst(cls, newName = None):
   cls.include()
   if newName is not None:
      cls.rename(newName)

def exposeSingleton(cls, newName=None):
   """ Include the class, rename it to a new name. """
   exposeTemplateInst(cls, newName)
   cls["the"].call_policies = return_value_policy(reference_existing_object)
   cls["_the"].exclude()

def removePureVirtuals(cls):
   """ Helper method to remove pure virtuals from class methods.
       This sets them to virtual so that boost::python::pure_virtual is not used
       but they still get the rest of the wrapper support.
   """
   import pygccxml.declarations as pd
   pure_members = cls.decls( virtuality_type_matcher( pd.VIRTUALITY_TYPES.PURE_VIRTUAL),
                           decl_type=pd.member_calldef_t,
                           allow_empty=True)
   pure_members.set_virtuality( pd.VIRTUALITY_TYPES.VIRTUAL ) 
   
def addStrOperator(cls):
   """ Just adds simple stupid def that uses op<<. """
   cls.add_registration_code('def(boost::python::self_ns::str(boost::python::self))',True)

class UnlockedNative(FT.transformer_t):
   def __init__(self, function):
      FT.transformer_t.__init__(self, function)

   def __str__(self):
      return "unlocked_native()"

   def required_headers(self):
      return []

   def __configureSealed(self, controller):
      # Create a guard for the GIL before the actual fucntion call is made.
      # This is an exception-safe approach to managing the GIL.
      controller.add_pre_call_code(
         'osgwrap::ReleaseGILGuard guard();'
      )

   def __configureDefault(self, controller):
      self.__configureSealed(controller)

   def configure_free_fun(self, controller):
      self.__configureSealed(controller)

   def configure_mem_fun(self, controller):
      self.__configureSealed(controller)

   def configure_virtual_mem_fun(self, controller):
      # For a virtual function, we only have to handle the defaualt call
      # case. A Python override of the C++ method needs to continue holding
      # the GIL.
      self.__configureDefault(controller.default_controller)

def unlocked_native():
   '''
   Returns a function transform creator for native functions and methods that
   do not hold the GIL while they execute.
   '''
   def creator(function):
      return UnlockedNative(function)

   return creator

# ----------------------------------------------------------- #
#            MAIN CODE GENERATION                             #
# ----------------------------------------------------------- #
def main():
   global timing
   import getpass
   username = getpass.getuser()
   
   # --- Collect all settings for build ---- #
   opensg_inc_dir = os.path.join(os.popen('osg2-config --prefix','r').readline().strip(),
                                 'include')
   opensg_cflags = os.popen('osg2-config --cflags System Base','r').readline().strip()
   opensg_cflags = opensg_cflags.replace("-use_readonly_const","")
   boost_cflags = "-DBOOST_PYTHON_MAX_ARITY=21"
   
   
   parser = optparse.OptionParser()
   parser.add_option("-s", "--osgsrc", dest = "opensg_src_dir",
                     type = "string", default = opensg_inc_dir,
                     help = "Path to OpenSG's Source directory")
   parser.add_option("-b", "--boostinc", dest = "boost_inc_dir",
                     type = "string",
                     help = "Path to Boost header parent directory")
   options, args = parser.parse_args()
   opensg_src_dir = options.opensg_src_dir
   boost_inc_dir  = options.boost_inc_dir

   if opensg_src_dir is None:
      print "ERROR: No path to OpenSG's Source directory provided!"
      sys.exit(1)
   if boost_inc_dir is None:
      print "ERROR: No path to Boost header directory provided!"
      sys.exit(2)

   python_inc_dir = distutils.sysconfig.get_python_inc()
   output_dir = "osg_module"
   main_header_filename = os.path.join(output_dir,'osgmodule_mainheader.h')
   defines = []              
   gccxml_path = ""
   inc_path = [output_dir, opensg_inc_dir, pj(opensg_inc_dir,"OpenSG"), 
               boost_inc_dir, python_inc_dir]
   
   # --- Search for .fcd files and build up class info ------- #
   print "Auto-building list of node cores using .fcd files... "
   fcd_files = []
   core_infos = core_info.core_infos   # Use dictionary so we could override in the future
   
   for p in ["Base","System","WindowSystem/GLUT"]:
      for dirpath,dirs,files in os.walk(os.path.join(opensg_src_dir,p)):         
         if not "Depreciated" in dirpath:
            fcd_files += [os.path.join(dirpath,f) for f in files if f.endswith(".fcd")]
   
   #print "found .fcd files: ", fcd_files
   if 1:
      for f in fcd_files:
         #print "fcd: ", f
         try:
            tree = et.parse(f)
            fc_elt = tree.getroot()
            assert fc_elt.tag == "FieldContainer"
            attribs = fc_elt.attrib.copy()
            parent = attribs["parent"]
            name = attribs["name"]
            doc = fc_elt.text.strip()
         except xml.parsers.expat.ExpatError, ex:
            #print "Error parsing file: %s"%f
            #print str(ex)
            #print "Trying re method instead. "
   
            file_text = file(f).read()
            name = re.search('name=\"(.*?)\"',file_text).group(1)
            parent = re.search('parent=\"(.*?)\"',file_text).group(1)
            library = re.search('library=\"(.*?)\"',file_text).group(1)
            doc = ""
   
         # Build up the class info
         #print "   name: %s  parent: %s"%(name,parent)
         if not core_infos.has_key(name) and not (name in core_info.core_ignore_list):
            core_infos[name] = core_info.CoreInfo(name,parent,documentation=doc)
   print "Node cores found: ", len(core_infos) 
   
   # ------ Manually define headers files -------- #
   # These includes are added later to the module builder
   extra_includes = ["OsgPtrHelpers.h",
                     "boost/python/suite/indexing/map_indexing_suite.hpp",
                     "boost/python/suite/indexing/vector_indexing_suite.hpp"]
   
   all_headers = []
   
   type_headers = ["OpenSG/OSGTypeBase.h",
                 "OpenSG/OSGMemoryObject.h",
                 "OpenSG/OSGDataType.h",
                 'OpenSG/OSGFieldType.h',
                 'OpenSG/OSGBaseTypes.h',
                 'OpenSG/OSGField.h',
                 #'OpenSG/OSGFieldFactory.h',
                 'OpenSG/OSGFieldDescriptionBase.h',
                 'OpenSG/OSGFieldContainerType.h',
                 'OpenSG/OSGFieldContainerFactory.h',
                 'OpenSG/OSGFieldContainer.h',              
                 'OpenSG/OSGField.h',              
                 'OpenSG/OSGChangeList.h',              
                 'OpenSG/OSGStageParent.h',
                 ]
                 
   math_headers = ["OpenSG/OSGLine.h",
                 "OpenSG/OSGVolume.h",           
                 "OpenSG/OSGVector.h",
                 "OpenSG/OSGColor.h",
                 "OpenSG/OSGMatrix.h",
                 'OpenSG/OSGQuaternion.h',
                 'OpenSG/OSGPlane.h',
                 'OpenSG/OSGDynamicVolume.h',
                 'OpenSG/OSGBoxVolume.h',
                 'OpenSG/OSGCylinderVolume.h',
                 'OpenSG/OSGFrustumVolume.h',
                 'OpenSG/OSGPolytopeVolume.h',
                 'OpenSG/OSGSphereVolume.h'                   
                 ]
                 
   free_func_headers = ['OpenSG/OSGStatCollector.h',
                 'OpenSG/OSGBaseFunctions.h',
                 'OpenSG/OSGFieldContainerPtr.h',
                 "OpenSG/OSGImageFileHandler.h",               
                 "OpenSG/OSGImageFileType.h",               
                 "OpenSG/OSGSceneFileHandler.h",               
                 "OpenSG/OSGSceneFileType.h",               
                 "OpenSG/OSGGraphOpSeq.h",
                 "OpenSG/OSGSimpleGeometry.h",
                 "OpenSG/OSGGeoFunctions.h",
                 "OpenSG/OSGMatrixUtility.h",
                 "OpenSG/OSGVolumeDraw.h",
                 "OpenSG/OSGConnectionFactory.h",               
                 ]
   
   misc_headers = ["OpenSG/OSGFieldContainerPtr.h",
                   #"OpenSG/OSGAttachmentPtr.h",
                   #'OpenSG/OSGAttachmentContainerPtr.h',
                   'OpenSG/OSGNode.h',
                   'OpenSG/OSGBinaryDataHandler.h',
                   #'OpenSG/OSGStringConversionStateBase.h',
                   'OpenSG/OSGNameAttachment.h',
                   #'OpenSG/OSGDrawAction.h',
                   'OpenSG/OSGEdgeIterator.h',
                   'OpenSG/OSGTriangleIterator.h',
                   'OpenSG/OSGPrimitiveIterator.h',
                   'OpenSG/OSGFaceIterator.h',
                   'OpenSG/OSGLineIterator.h',
                   'OpenSG/OSGState.h',
                   'OpenSG/OSGBackground.h',
                   'OpenSG/OSGLightEnv.h',
                   'OpenSG/OSGDrawTreeNode.h',
                   #'OpenSG/OSGBrick.h',
                   "OpenSG/OSGGeoTypeGraphOp.h",
                   "OpenSG/OSGGraphOpFactory.h",
                   "OpenSG/OSGGraphOp.h",
                   "OpenSG/OSGGraphOpSeq.h",
                   "OpenSG/OSGMakeTransparentGraphOp.h",
                   "OpenSG/OSGMaterialMergeGraphOp.h",
                   "OpenSG/OSGMergeGraphOp.h",
                   "OpenSG/OSGPruneGraphOp.h",
                   "OpenSG/OSGSharePtrGraphOp.h",
                   "OpenSG/OSGSingleTypeGraphOp.h",
                   "OpenSG/OSGSplitGraphOp.h",
                   "OpenSG/OSGStripeGraphOp.h",
                   "OpenSG/OSGVerifyGeoGraphOp.h",
                   "OpenSG/OSGVerifyGraphOp.h",
                   "OpenSG/OSGException.h",
                   "OpenSG/OSGClusterException.h",
                   "OpenSG/OSGExceptionBinaryDataHandler.h",
                   "OpenSG/OSGSocketException.h",
                   "OpenSG/OSGPerfMonitor.h"
                   ]

   wrapper_headers = ["ColorWrapper.h", 
                    "Wrappers.h",
                    "VecStorageWrappers.h",]
   workaround_headers = ["aliases.h"]

   core_headers = []
      
   core_infos = core_info.core_infos
   for c in core_infos.itervalues():
      core_headers.append("OpenSG/OSG%s.h"%c.name)
      #if c.parent:
      #   core_headers.append("OpenSG/OSG%sBase.h"%c.parent)   
      core_headers.extend(c.includes)
   
   for class_info in core_info.gen_classes:
      core_headers.append("OpenSG/OSG%s.h"%class_info.name)   
      core_headers.extend(class_info.includes)
      
   all_headers = type_headers + math_headers + free_func_headers + misc_headers
   all_headers.extend(wrapper_headers + workaround_headers + core_headers)
   
   all_headers_full_paths = []
   for f in all_headers:
      for p in inc_path:
         if os.path.isfile(pj(p,f)):
            all_headers_full_paths.append(pj(p,f))
            continue
   
   #all_headers = base_osg_headers + wrapper_headers
   
   #print "Core: ", core_headers
   
   print "Building PyOpenSG module...."
   print "setting up templates..."
   template_builder = LocalTemplateBuilder()
   tbuilder = TemplateBuilder()   
   
   # Add the math templates
   for vst in settings.vec_storage_typeinfo:
      template_builder.Template(vst.type, vst.alias)
   
   for vt in settings.vec_typeinfo:
      template_builder.Template(vt.type, vt.alias)
      
   for pt in settings.pnt_typeinfo:
      template_builder.Template(pt.type, pt.alias) 
   
   #pnt_templates = []
   #for size in settings.pnt_sizes:
   #   for type_str in settings.pnt_types.iteritems():
   #      pnt_temp = tb.Template("OSG::Pnt%s%s"%(size, type_str),
   #                             "Pnt%s%s"%(size,type_str))
      
   for ct in settings.color_typeinfo:
      template_builder.Template(ct.type, ct.alias) 
         
   
   mat_temp = tbuilder.Template("OSG::Matrix", "Matrix")
   matd_temp = tbuilder.Template("OSG::Matrix4d", "Matrix4d")
   quat_temp = tbuilder.Template("OSG::Quaternion","Quaternion")
   quatd_temp = tbuilder.Template("OSG::Quaterniond","Quaterniond")
   
   sfh_temp = tbuilder.Template("OSG::SceneFileHandler","SceneFileHandler")
   ifh_temp = tbuilder.Template("OSG::ImageFileHandler","ImageFileHandler")
   fcf_temp = tbuilder.Template("OSG::FieldContainerFactory","FieldContainerFactory")
   rtnp_temp = tbuilder.Template("OSG::RenderTreeNodePool",
                                 "RenderTreeNodePool")
   sop_temp = tbuilder.Template("OSG::StateOverridePool", "StateOverridePool")
   #tbp_temp = tbuilder.Template("OSG::TreeBuilderPool", "TreeBuilderPool")
   scp_temp = tbuilder.Template("OSG::ContainerMixinHead<OSG::StageDesc>",
                                "StageContainerParent")
   sp_temp = tbuilder.Template("OSG::StageParent", "StageParent")
   cf_temp = tbuilder.Template("OSG::ConnectionFactory", "ConnectionFactory")

   pm_temp = tbuilder.Template("OSG::PerfMonitor","PerfMonitor")

   stat_templates = []
   for x in ["StatIntElem", "StatTimeElem", "StatRealElem", "StatIntOnceElem"]:
      temp = tbuilder.Template("OSG::StatElemDesc<OSG::%s>" % x,
                               "StatElemDesc_%s" % x)
      stat_templates.append(temp)

   # Build core template instantiations
   template_instance_text  = "\n\n// NodeCore template instantiations\n"
   template_instance_text += "#if defined(__GCCXML__)\n"
   for c in core_infos.itervalues():
      for t in c.templateInstances:
         inst_text = "%s;\n"%t
         template_instance_text = template_instance_text + inst_text
   template_instance_text += "#endif\n"

   # Specify manual template instantiations.
   forced_template_instances = []
   # XXX: Currently there is a bug (#5439) in gccxml that does all explicit member template
   #      instantiation of templated classes. For now we will have to manually wrap these methods.
   #
   #forced_template_instances.extend(["template void OSG::Matrix4f::mult(const OSG::TransformationMatrix<%s>&)" % \
   #   (t, t) for t in ['OSG::Real32', 'OSG::Real64']])
   #forced_template_instances.extend(["template void OSG::Matrix4d::mult(const OSG::TransformationMatrix<%s>&)" % \
   #   (t, t) for t in ['OSG::Real32', 'OSG::Real64']])
   #forced_template_instances.extend(["template void OSG::Matrix4f::multLeft(const OSG::TransformationMatrix<%s>&)" % \
   #   (t, t) for t in ['OSG::Real32', 'OSG::Real64']])
   #forced_template_instances.extend(["template void OSG::Matrix4d::multLeft(const OSG::TransformationMatrix<%s>&)" % \
   #   (t) for t in ['OSG::Real32', 'OSG::Real64']])

   # Add all manually specified template instantiations.
   #template_instance_text += "\n\n// Manual template instantiations\n"
   #template_instance_text += "#if defined(__GCCXML__)\n"
   #for t in forced_template_instances:
   #   template_instance_text += "%s;\n" % t
   #template_instance_text += "#endif\n"

   # Geo vector template types
   geo_vec_prop_templates = []
   for x in settings.geo_vec_prop_names:
      geo_vec_prop_templates.append(tbuilder.Template("OSG::%s"%x, x))
   geo_int_prop_templates = []
   for x in settings.geo_integral_prop_names:
      geo_int_prop_templates.append(tbuilder.Template("OSG::%s"%x, x))
   
   # Parse with builder
   print "  creating header file:", main_header_filename
   main_header_filename = buildContainerHeader(all_headers, 
                           template_builder.buildTemplateFileContents() + \
                           tbuilder.buildAutogenContents() + \
                           template_instance_text,
                           filename=main_header_filename)
   
   print "creating builder and parsing."
   cache_file = "pypp.pyopensg.cache"
   timing.append(["collect headers and templates",time.time()])
   
   mb = ModuleBuilder([main_header_filename],
                      working_directory = ".",
                      include_paths=inc_path,
                      cache=cache_file+".module",
                      define_symbols=defines,
                      ignore_gccxml_output=True,
                      optimize_queries=use_query_opt,
                      #start_with_declarations=["OSG",],
                      cflags=opensg_cflags + " " + boost_cflags)
                      #dependent_headers=all_headers_full_paths)
   tbuilder.processTemplates(mb)
   mb.BOOST_PYTHON_MAX_ARITY = 21
   mb.global_ns.exclude()
   osg = mb.global_ns.namespace("OSG", recursive=False)   
   
   timing.append(["module builder init",time.time()])
   
   if False:
      decl_file = file("decl_out_early.txt",'w', 1024000)
      print "Writing out decls to file... ",
      mb.print_declarations(osg, writer=decl_file.write)
      #mb.print_declarations(my_class)
      decl_file.close()
      print "done."
   
   #mb.print_declarations()
   #mb.print_declarations(my_class)
   
   # ------------ Helper methods and data -------------------- #
   
   # Map from template alias name to real decl wrapper type
   print "Building template alias map..."
   template_alias_db = {}
   osg_typedef_db = {}

   global_typedefs = mb.global_ns.typedefs()   
   for td in global_typedefs:
      template_alias_db[td.name] = td.type

   osg_typedefs = osg.typedefs()

   def getOsgTypedefFromType(_type, typedefs = osg_typedefs):
      """ Given a type, look up the original typedef. """
      type_name = _type.declaration.name

   #print "Typedefs: "
   #for (name, _type) in template_alias_db.iteritems():
   #   print "  %s: %s"%(name, _type) 

   #typedefs = osg.typedefs()
   #print "OSG Typedefs:"
   #for td in typedefs:
   #   print "%s -- %s -- %s"%(td.name, td.alias, td.type)
   
   #typedefs = mb.global_ns.typedefs()
   #print "Global Typedefs:"
   #for td in typedefs:
   #   print "%s -- %s -- %s"%(td.name, td.alias, td.type)
         
   
   print "# ---------------  Base Memory and Field Types ----------------- #"
   
   # Expose classes
   # - Memory/Type classes
   tb = osg["TypeBase"]
   tb.include()
   tb["GlobalNamespace"].exclude()
   handleClassDefaults(tb)
   tb["getParent"].call_policies = return_internal_reference()
   finalize(tb)

   mo = osg["MemoryObject"]
   mo.include()

   # Do not let Py++ think that it needs to generate a wrapper for
   # OSG::MemoryObject to expose the constructor and copy constructor for the
   # class.
   for c in mo.constructors():
      c.exclude()

   # Be sure that OSG::MemoryObject instances cannot be copied. The copy
   # constructor is protected in the declaration, and this is here just to be
   # sure that Py++ gets it right.
   for d in mo.declarations:
      d.noncopyable = True

   dt = osg["DataType"]
   dt.include()
   handleClassDefaults(dt)
   finalize(dt)
   
   # - Field classes
   ft = osg["FieldType"]
   ft.include()
   finalize(ft)
   
   #ff = decl_from_typedef( osg["FieldFactory"] )
   #ff.include()
   #ff["createField"].call_policies = return_value_policy(manage_new_object)
   #ff["getFieldType"].call_policies  = return_internal_reference()
   #ff["the"].call_policies = return_value_policy(reference_existing_object)
   #ff["_fieldTypeM"].exclude()
   #finalize(ff)
   
   fd = osg["FieldDescriptionBase"]
   fd.include()
   for x in ["_fEditMethod","_fGetMethod","_fIndexedGetMethod","_fIndexedEditMethod","cloneValuesV","shareValuesV"]:
      # ["cloneValuesV","shareValuesV",]
      fd[x].exclude()
   for n in ["getField","editField","getFieldType"]:
      fd[n].call_policies = return_internal_reference()
   fd["createField"].call_policies = return_value_policy(manage_new_object)
   fd["clone"].call_policies = return_value_policy(manage_new_object)
   finalize(fd)

   rct = osg.class_("ReflexiveContainerType")    #.finalize()   
   rct.include()
   finalize(rct)
   handleClassDefaults(rct)

   
   fct = osg.class_("FieldContainerType")    #.finalize()   
   fct.include()
   finalize(fct)
   handleClassDefaults(fct)
   
   ##fct["getParent"].exclude()
   #fct["addDescription"].exclude()
   #fct["subDescription"].exclude()
   #fct["getFieldDesc"].call_policies = return_internal_reference()
   ##fct["findFieldDescription"].call_policies = return_internal_reference()   
      
   # FieldContainerFactory   
   # Note: We have to pull some members in because they are part of the base class templates
   #        there may be a way to do this more directly (see gccxml-devel list) but I have not done it yet.
   fcf = fcf_temp.decl
   exposeSingleton(fcf,"FieldContainerFactory")
   fcf_base = osg["FieldContainerFactoryBase"]
   fcf_base.include()
   finalize(fcf_base)
   handleClassDefaults(fcf_base)   
   add_member_function(fcf_base, "getContainer","&OSG::FieldContainerFactoryBase::getContainer")
   add_member_function(fcf_base, "getNumGroups","&OSG::FieldContainerFactoryBase::getNumGroups")
   add_member_function(fcf_base, "findGroupName","&OSG::FieldContainerFactoryBase::findGroupName")

   #for c in [c.related_class for c in fcf_base[0].recursive_bases]:
   #   c.include()
   #   handleClassDefaults(c)
   
   # Special case of mixins for fc's
   cacp_typedef = osg["ContainerAttachmentContainerParent"]
   cacp = decl_from_typedef(cacp_typedef)
   cacp.include()
   finalize(cacp)
   handleClassDefaults(cacp)
   
   fcap_typedef = osg["FieldContainerAttachmentParent"]
   fcap = decl_from_typedef(fcap_typedef)
   fcap.include()
   finalize(fcap)
   handleClassDefaults(fcap)

   timing.append(["base field types",time.time()])

   # Expose instantiations of OSG::SimplePool<T>.
   for d in [rtnp_temp.decl, sop_temp.decl]:
      exposeTemplateInst(d)
      d["create"].call_policies = return_internal_reference()

   #exposeTemplateInst(tbp_temp.decl)

   scp_temp_decl = scp_temp.decl
   exposeTemplateInst(scp_temp_decl)
   for c in scp_temp_decl.constructors():
      c.exclude()

   # XXX: This is a hack to avoid unresolved symbol errors. I believe that the
   # problem is that instantiations of OSG::ContainerMixinHead<T> fail to
   # define these static members.
   scp_temp_decl["NextFieldId"].exclude()
   scp_temp_decl["NextFieldMask"].exclude()

   sp_temp_decl = sp_temp.decl
   exposeTemplateInst(sp_temp_decl)
   sp_temp_decl["editSFUpdateMode"].exclude()
   sp_temp_decl["getSFUpdateMode"].call_policies = return_internal_reference()
   for c in sp_temp_decl.constructors():
      c.exclude()

   # XXX: This is a hack to avoid unresolved symbol errors. I believe that the
   # problem is that OSG::StageParent (a typedef for a template class) fails
   # to define these static members.
   sp_temp_decl["NextFieldId"].exclude()
   sp_temp_decl["NextFieldMask"].exclude()
   sp_temp_decl["UpdateModeFieldId"].exclude()
   sp_temp_decl["UpdateModeFieldMask"].exclude()

   # Performance monitor
   pm = pm_temp.decl
   pm.include()
   # Expose the singleton.
   exposeSingleton(pm, "PerfMonitor")
   pm_base = osg["PerfMonitorBase"]
   pm_base.include()
   finalize(pm_base)
   #handleClassDefaults(bl_base)
   nsi = osg["NestedSampleInfo"]
   nsi.include()
   nsi['string_hash'].exclude()
   finalize(nsi)

   # ----------------- Base Math types --------------- #
   print "---------------------------------------------------"
   print "Base Math Type"
   
   # Expose classes   
   line = osg.class_("Line")    #.finalize()
   line.include()
   line.noncopyable = False
   finalize(line)
   
   # XXX: Remove constructor that takes pointer types
   # Expose the VecStorage classes
   for vst in settings.vec_storage_typeinfo:
      (size,type_abrv) = (ct.size,ct.type_str)
      cname = template_alias_db[vst.alias].declaration.name       # Lookup true class name by alias
      c = osg.class_(cname)
      #c.noncopyable = False
      c.include()
      c.rename(vst.exposed_name)
      c["_uiSize"].exclude()
      wrap_method(c,"getSeparateValues", 
                   "&osgwrap::VecStorage%s_getSeparateValues_tuple<%s>"%(size,type_abrv))
      #addStrOperator(c)
      exclude_protected(c)
   
   # VecXX classes
   for vt in settings.vec_typeinfo:      
      c = template_alias_db[vt.alias].declaration.type.declaration
      c.noncopyable = False
      c.include()
      c.rename(vt.exposed_name)
      c["Null"].exclude()
      c["subZero"].call_policies = return_internal_reference()
      c["addToZero"].call_policies = return_internal_reference()
      add_member_function(c,"__setitem__","osgwrap::setArrayElement<%s,%s::ValueType>"%(vt.type,vt.type))
      addStrOperator(c)
      exclude_protected(c)
   
   # PntXX classes
   for pt in settings.pnt_typeinfo:
      #type_name = template_alias_db[pt.alias].declaration.name
      c = template_alias_db[pt.alias].declaration.type.declaration
      c.include()
      c.noncopyable = False
      #c = osg.class_(cname)
      c.rename(pt.exposed_name)
      c["Null"].exclude()
      c["subZero"][0].call_policies = return_internal_reference()
      #c["addToZero"][0].call_policies = return_internal_reference()
      wrap_method(c,"getValues", "osgwrap::getValues%d<%s>"%(pt.size,pt.type))
      add_member_function(c,"__setitem__","osgwrap::setArrayElement<%s,%s::ValueType>"%(pt.type,pt.type))
      addStrOperator(c)
      exclude_protected(c)
   
   # ColorXX classes
   for ct in settings.color_typeinfo:
      (size,type_abrv) = (ct.size,ct.type_str)
      c = template_alias_db[ct.alias].declaration.type.declaration
      c.include()
      c.rename(ct.exposed_name)
      if 3 == size:
         wrap_method(c,"getValuesRGB", "Color3_getValuesRGB_tuple<%s>"%type_abrv)
      else:         
         wrap_method(c,"getValuesRGBA", "Color4_getValuesRGBA_tuple<%s>"%type_abrv)
      wrap_method(c,"getValuesHSV", "Color_getValuesHSV< %s >"%ct.type)
      c.decls("convertToHSV",allow_empty=True).exclude()
      add_member_function(c,"__setitem__","osgwrap::setArrayElement<%s,%s::ValueType>"%(ct.type,ct.type))
      addStrOperator(c)
      exclude_protected(c)
      c.noncopyable = False

      
   import pygccxml.declarations as pd

   # Matrix
   #c = template_alias_db["alias_Matrix"].declaration.type.declaration
   c = mat_temp.decl
   c.noncopyable = False
   c.include()
   finalize(c)

   # Remove the operator that returns a vector by const reference because it
   # results in returning a copy.
   for array_op in c['operator[]']:
      if is_const_ref(array_op.return_type):
         array_op.exclude()

   # Does not work for some reason. So we do it manually.
   #c.member_function(name='setValue', arg_types = [pd.pointer_t(pd.const_t(pd.float_t())), pd.bool_t()]).exclude()

   for svm in c['setValue']:
      if 2 == len(svm.arguments):
         arg0 = svm.arguments[0]
         if pd.is_pointer(arg0._type) and pd.is_floating_point(pd.remove_pointer(arg0._type)):
            svm.exclude()
   c.add_registration_code('def("setValue", &osgwrap::setValueMatrix<float>, ( bp::arg("pMat"), bp::arg("bTransposed")=(bool)(true) ) )')

   wrap_method(c,"getValues", 'osgwrap::getValuesMatrix<float>')     
   wrap_method(c,"getTransform", 'osgwrap::getTransform_Matrix<float>')

   # Expose all mult and multLeft variations.
   matrix_value_types = ['OSG::Real32', 'OSG::Real64']
   for right_type in matrix_value_types:
      add_member_function(c, "mult", "osgwrap::wrapMatrixMult<OSG::Real32, %s>" % right_type)
      add_member_function(c, "multLeft", "osgwrap::wrapMatrixMultLeft<OSG::Real32, %s>" % right_type)

   c["jacobi"].exclude()
   addStrOperator(c)
   exclude_protected(c)

   # Expose Matrix4d
   c = matd_temp.decl
   c.noncopyable = False
   c.include()
   finalize(c)

   # Remove the operator that returns a vector by const reference because it
   # results in returning a copy.
   for array_op in c['operator[]']:
      if is_const_ref(array_op.return_type):
         array_op.exclude()

   # Does not work for some reason. So we do it manually.
   #c.member_function(name='setValue', arg_types = [pd.pointer_t(pd.const_t(pd.float_t())), pd.bool_t()]).exclude()

   for svm in c['setValue']:
      if 2 == len(svm.arguments):
         arg0 = svm.arguments[0]
         if pd.is_pointer(arg0._type) and pd.is_floating_point(pd.remove_pointer(arg0._type)):
            svm.exclude()
   c.add_registration_code('def("setValue", &osgwrap::setValueMatrix<double>, ( bp::arg("pMat"), bp::arg("bTransposed")=(bool)(true) ) )')

   wrap_method(c,"getValues", 'osgwrap::getValuesMatrix<double>')
   wrap_method(c,"getTransform", 'osgwrap::getTransform_Matrix<double>')

   # Expose all mult and multLeft variations.
   matrix_value_types = ['OSG::Real32', 'OSG::Real64']
   for right_type in matrix_value_types:
      add_member_function(c, "mult", "osgwrap::wrapMatrixMult<OSG::Real64, %s>" % right_type)
      add_member_function(c, "multLeft", "osgwrap::wrapMatrixMultLeft<OSG::Real64, %s>" % right_type)

   c["jacobi"].exclude()
   addStrOperator(c)
   exclude_protected(c)

   # Quaternion
   c = quat_temp.decl
   c.noncopyable = False
   c.include()
   finalize(c)
   c["_uiSize"].exclude()
   wrap_method(c,"getValues", 'osgwrap::getValues4<OSG::QuaternionBase<OSG::Real32> >')  
   wrap_method(c,"getValueAsQuat", 'osgwrap::getValues4<OSG::QuaternionBase<OSG::Real32> >')
   wrap_method(c,"getValueAsAxisDeg", 'osgwrap::getValueAsAxisDeg<OSG::Real32>')
   wrap_method(c,"getValueAsAxisRad", 'osgwrap::getValueAsAxisRad<OSG::Real32>')
   add_member_function(c,"__setitem__","osgwrap::setArrayElement<OSG::Quaternion,OSG::Quaternion::ValueType>")
   addStrOperator(c)
   exclude_protected(c)

   c = quatd_temp.decl
   c.noncopyable = False
   c.include()
   finalize(c)
   c["_uiSize"].exclude()
   wrap_method(c,"getValues", 'osgwrap::getValues4<OSG::QuaternionBase<OSG::Real64> >')  
   wrap_method(c,"getValueAsQuat", 'osgwrap::getValues4<OSG::QuaternionBase<OSG::Real64> >')
   wrap_method(c,"getValueAsAxisDeg", 'osgwrap::getValueAsAxisDeg<OSG::Real64>')
   wrap_method(c,"getValueAsAxisRad", 'osgwrap::getValueAsAxisRad<OSG::Real64>')
   add_member_function(c,"__setitem__","osgwrap::setArrayElement<OSG::Quaterniond,OSG::Quaterniond::ValueType>")
   addStrOperator(c)
   exclude_protected(c)
   
   # Plane
   c = osg.class_("Plane")
   c.include()
   c.noncopyable = False
   c["getNormal"].call_policies = return_internal_reference(1)
   # These transformations have to have the alias set explicitly or else Py++
   # will generate the "ugly" alias form as the name of the Python method.
   c["intersect"].add_transformation(FT.output(1), alias = "intersect")
   c["intersectInfinite"].add_transformation(FT.output(1),
                                             alias = "intersectInfinite")
   exclude_protected(c)

   for vol_name in ["Volume","DynamicVolume",
                    "BoxVolume","CylinderVolume","FrustumVolume","PolytopeVolume","SphereVolume"]:
      c = osg[vol_name][0]
      c.include()   
      finalize(c)         
      handleClassDefaults(c)
      c.decls("getPlanes",allow_empty=True).exclude()
      if "DynamicVolume" == vol_name:
         c["getType"].call_policies = default_call_policies()
      exclude_protected(c)
   
   timing.append(["math types",time.time()])
      
   print "----------------[ Non-FC Classes ]-------------"
   
   # Standard classes
   for class_info in core_info.gen_classes:
      print "Class: %s"%class_info.name
   
      c = osg.class_(class_info.name)
      c.include()
      #base_c.finalize()
      handleClassDefaults(c)
      for x in class_info.excludes:            
         c[x].exclude()
      for (n,p) in class_info.policies:
         c[n].call_policies = p      
      if class_info.use_shared_ptr:
         c.held_type = "boost::shared_ptr<OSG::%s>"%class_info.name
      for d in class_info.declarations:
         c.add_declaration_code(d)
      for r in class_info.registrations:
         c.add_registration_code(r)
      if class_info.modCallable:         # Call any customizations needed
         class_info.modCallable(c)
      if class_info.finalize:
         finalize(c)

   # XXX: Exclude the problematic two parameter version of apply that takes iterators.
   action = osg.class_('Action')
   for aa in action['apply']:
      if 2 == len(aa.arguments):
         aa.exclude()

   c = sfh_temp.decl
   c.include()
   exposeSingleton(c,"SceneFileHandler")
   sfh_base = osg["SceneFileHandlerBase"]
   sfh_base.include()
   finalize(sfh_base)
   handleClassDefaults(sfh_base)
   for m in ["getPathHandler", "getDefaultGraphOp", "getOptions", "setReadCB",
             "getReadCB", "setWriteCB", "getWriteCB", "getReadProgressCB",
             "setReadProgressCB", "setWriteProgressCB", "getWriteProgressCB",
             "progressS"
             ]:
      sfh_base[m].exclude()
   # Need to do this because _defaultgraphOp is protected and can't be used as a default
   sfh_base.calldefs("read").exclude()           #use_default_arguments = False   
   sfh_base.calldefs("readTopNodes").exclude()   #use_default_arguments = False   
   sfh_base.add_declaration_code("""
OSG::NodePtr sfh_custom_read(OSG::SceneFileHandlerBase& sfh,
                             const std::string& fileName)
{
   return sfh.read(fileName.c_str());
}

bp::list wrapGetSuffixList(OSG::SceneFileHandlerBase& sfh,
                           const OSG::UInt32 flags)
{
   std::list<const OSG::Char8*> list;
   sfh.getSuffixList(list, flags);
   bp::list python_list;

   typedef std::list<const OSG::Char8*>::iterator iter_type;
   for ( iter_type i = list.begin(); i != list.end(); ++i )
   {
      python_list.append(*i);
   }

   return python_list;
}
""")
   add_member_function(sfh_base, "read", "sfh_custom_read")
   sfh_base["getSuffixList"].exclude()
   sfh_base.add_registration_code("""
      def("getSuffixList", wrapGetSuffixList,
          (bp::arg("flags") = OSG::SceneFileType::OSG_READ_SUPPORTED |
                              OSG::SceneFileType::OSG_WRITE_SUPPORTED))
   """)

   # Include ImageFileType
   ift = osg["ImageFileType"]
   ift.include()
   ift.add_declaration_code("""
bp::list wrapGetSuffixList(OSG::ImageFileType& ift)
{
   std::list<std::string> list = ift.getSuffixList();
   bp::list python_list;

   typedef std::list<std::string>::iterator iter_type;
   for ( iter_type i = list.begin(); i != list.end(); ++i )
   {
      python_list.append(*i);
   }

   return python_list;
}
""")
   ift["getSuffixList"].exclude()
   ift.add_registration_code("""
      def("getSuffixList", wrapGetSuffixList)
   """)

   # ImageFileHandler
   c = ifh_temp.decl
   c.include()
   exposeSingleton(c,"ImageFileHandler")
   ifh_base = osg["ImageFileHandlerBase"]
   ifh_base.include()
   finalize(ifh_base)
   handleClassDefaults(ifh_base)
   for m in ["getPathHandler", "getOptions", "setReadCB", "getReadCB", "store"]:
      ifh_base[m].exclude()
   # Need to do this because _defaultgraphOp is protected and can't be used as a default
   #ifh_base.calldefs("read").exclude()           #use_default_arguments = False   
   #ifh_base.calldefs("readTopNodes").exclude()   #use_default_arguments = False   
   ifh_base.add_declaration_code("""
bp::list wrapGetSuffixList(OSG::ImageFileHandlerBase& ifh,
                           const OSG::UInt32 flags)
{
   std::list<const OSG::Char8*> list;
   ifh.getSuffixList(list, flags);
   bp::list python_list;

   typedef std::list<const OSG::Char8*>::iterator iter_type;
   for ( iter_type i = list.begin(); i != list.end(); ++i )
   {
      python_list.append(*i);
   }

   return python_list;
}

bp::dict wrapGetMimeTypeMap(OSG::ImageFileHandlerBase& ifh)
{
   bp::dict python_dict;

   const OSG::ImageFileHandlerBase::TypeMap& mime_type_map = ifh.getMimeTypeMap();
   typedef OSG::ImageFileHandlerBase::TypeMap::const_iterator iter_type;
   for (iter_type itr = mime_type_map.begin(); itr != mime_type_map.end(); itr++)
   {
      python_dict[(*itr).first] = bp::ptr((*itr).second);
   }

   return python_dict;
}

bp::dict wrapGetSuffixTypeMap(OSG::ImageFileHandlerBase& ifh)
{
   bp::dict python_dict;

   const OSG::ImageFileHandlerBase::TypeMap& mime_type_map = ifh.getSuffixTypeMap();
   typedef OSG::ImageFileHandlerBase::TypeMap::const_iterator iter_type;
   for (iter_type itr = mime_type_map.begin(); itr != mime_type_map.end(); itr++)
   {
      python_dict[(*itr).first] = bp::ptr((*itr).second);
   }

   return python_dict;
}

""")
   #add_member_function(ifh_base, "read", "ifh_custom_read")
   ifh_base["getSuffixList"].exclude()
   ifh_base.add_registration_code("""
      def("getSuffixList", wrapGetSuffixList,
          (bp::arg("flags") = OSG::ImageFileType::OSG_READ_SUPPORTED |
                              OSG::ImageFileType::OSG_WRITE_SUPPORTED))
   """)
   ifh_base["getMimeTypeMap"].exclude()
   ifh_base.add_registration_code('def("getMimeTypeMap", wrapGetMimeTypeMap)')
   ifh_base["getSuffixTypeMap"].exclude()
   ifh_base.add_registration_code('def("getSuffixTypeMap", wrapGetSuffixTypeMap)')

   # OSG::ConnectionFactory
   c = cf_temp.decl
   c.include()
   exposeSingleton(c, "ConnectionFactory")
   cf_base = osg["ConnectionFactoryBase"]
   cf_base.include()
   # OSG::ConnectionFactoryBase::dump() is declared but not defined.
   cf_base["dump"].exclude()
   finalize(cf_base)
   handleClassDefaults(cf_base)

   # Expose stat classes
   sedb = osg["StatElemDescBase"]
   sedb.include()
   handleClassDefaults(sedb)
   sedb["findDescByName"].exclude()
   #finalize(sedb)
   
   for t in stat_templates:
      c = t.decl
      c.include()
      finalize(c)

   ebdh = osg.class_("ExceptionBinaryDataHandler")

   for f in ebdh.member_functions("getValue"):
      arg_type = remove_cv(remove_reference(f.argument_types[0]))
      if type(arg_type) == pd.cpptypes.bool_t:
         f.add_transformation(FT.output(0), alias = "getBool")
      elif type(arg_type) == pd.cpptypes.signed_char_t:
         f.add_transformation(FT.output(0), alias = "getInt8")
      elif type(arg_type) == pd.cpptypes.short_int_t:
         f.add_transformation(FT.output(0), alias = "getInt16")
      elif type(arg_type) == pd.cpptypes.int_t:
         f.add_transformation(FT.output(0), alias = "getInt32")
      elif arg_type.decl_string == 'OSG::Int64':
         f.add_transformation(FT.output(0), alias = "getInt64")
      elif type(arg_type) == pd.cpptypes.float_t:
         f.add_transformation(FT.output(0), alias = "getReal32")
      elif type(arg_type) == pd.cpptypes.double_t:
         f.add_transformation(FT.output(0), alias = "getReal64")
      elif type(arg_type) == pd.cpptypes.long_double_t:
         f.add_transformation(FT.output(0), alias = "getReal128")
      elif (arg_type.decl_string == 'std::string' or
            arg_type.decl_string == '::std::string'):
         f.add_transformation(FT.output(0), alias = "getString")
      else:
         #print "Excluding return type", arg_type.decl_string
         f.exclude()

   for f in ebdh.member_functions("putValue"):
      arg_type = remove_cv(remove_reference(f.argument_types[0]))
      if type(arg_type) == pd.cpptypes.bool_t:
         f.alias = "putBool"
      elif type(arg_type) == pd.cpptypes.signed_char_t:
         f.alias = "putInt8"
      elif type(arg_type) == pd.cpptypes.short_int_t:
         f.alias = "putInt16"
      elif type(arg_type) == pd.cpptypes.int_t:
         f.alias = "putInt32"
      elif arg_type.decl_string == 'OSG::Int64':
         f.alias = "putInt64"
      elif type(arg_type) == pd.cpptypes.float_t:
         f.alias = "putReal32"
      elif type(arg_type) == pd.cpptypes.double_t:
         f.alias = "putReal64"
      elif type(arg_type) == pd.cpptypes.long_double_t:
         f.alias = "putReal128"
      elif (arg_type.decl_string == 'std::string' or
            arg_type.decl_string == '::std::string'):
         f.alias = "putString"
      else:
         #print "Excluding data type", arg_type.decl_string
         f.exclude()

   # Graph ops   
   for n in ["GraphOp", "MakeTransparentGraphOp", "MaterialMergeGraphOp",
             "PruneGraphOp", "SharePtrGraphOp", "VerifyGeoGraphOp",
             "GeoTypeGraphOp", "StripeGraphOp", "SplitGraphOp",
             "VerifyGraphOp", "MergeGraphOp"]:
      op = osg.class_(n)
      op.include()
      handleClassDefaults(op)
      op.decls("traverseEnter", allow_empty = True).include()
      op.decls("traverseLeave", allow_empty = True).include()

   # ----------------- Free functions and basic capabilities --------------- #
   print "---------------------------------------------------"
   print "Misc stuff"
   
   # Base methods
   osg["osgInit"].exclude()     # Add wrapper below to code creator
   osg_exit = osg["osgExit"]
   osg_exit.include()
   
   #osg["beginEditCP"].include()
   #osg["endEditCP"].include()
   #osg["NullFC"].exclude()
   
   add_method(mb,"getRefCount","&osgwrap::ptrGetRefCount")
   add_method(mb,"getContainerId","&osgwrap::ptrGetContainerId")   
   add_method(mb, "osgInit", "&osgwrap::osgInitWrapper")

   osg["traverse"].exclude()
   add_method(mb, "traverse", "&osgwrap::traverseWrapper1")
   add_method(mb, "traverse", "&osgwrap::traverseWrapper2")
   add_method(mb, "traverse", "&osgwrap::traverseWrapper3")
   add_method(mb, "traverse", "&osgwrap::traverseWrapper4")
   
   # Public methods
   for m in core_info.public_methods:
      #print "m: %s"%m
      funcs = osg.decls(m)
      funcs.include()
      for f in funcs:
         f.call_policies = fc_policy_resolver(f)
         for mod_callable in core_info.public_method_mods.get(m,[]):
            mod_callable(f)

   mat_util = ["MatrixOrthogonal", "MatrixFrustum", "MatrixPerspective",
               "MatrixStereoPerspective", "MatrixLookAt", "MatrixProjection"]
   for f in mat_util:
      decl = osg[f]
      decl.include()

   osg["drawVolume"].include()

   timing.append(["non-fc types and free funcs",time.time()])

   # --- Exception Translation  --- #
   print "---------------------------------------------------"
   print "Exception Translations"

   # OpenSG exceptions to be translated into a Python exception type.
   exc_types = [("ClusterException", "IOError"),
                ("SocketException", "IOError"),
                ("ExceptionBinaryDataHandler::ReadError", "IOError"),
                ("ExceptionBinaryDataHandler::WriteError", "IOError"),
                ("Exception", "Exception")
               ]

   for t in exc_types:
      class_hierarchy = t[0].split('::')
      ex_type_name = class_hierarchy.pop()
      ns = osg
      for c in class_hierarchy:
         ns = ns.class_(c)
      ex_type = ns.class_(ex_type_name)
      ex_type.translate_exception_to_string('PyExc_%s' % t[1], 'exc.what()')
      ex_type.include()
      finalize(ex_type)

   timing.append(["exception translation", time.time()])

   # ----------- Generate code for all the Field Containers  --- #
   print "---------------------------------------------------"
   print "Field Containers"
   
   fc = osg.class_("FieldContainer") 
   fc.include()
   for m in ["onCreate","onCreateAspect","onDestroy",
             "NextFieldMask",'isNodeCore',
             "editSField","editMField","getFieldFlags",
             "pushToField","insertIntoMField","replaceInMField",
             "replaceInMField","removeFromMField","removeFromMField","clearField",
             "registerChangedContainer","registerChangedContainerV",
             "execSync","deregister"]:
             #"execSyncV"]:
      fc[m].exclude()
   for m in ["getType"]:  # "getField"]:
      fc[m].call_policies = return_internal_reference()

   # getClassType() is a static method that returns a reference to an existing
   # object. Because it is a static method, it cannot use
   # return_internal_refernce because there is no custodian object.
   fc["getClassType"].call_policies = return_value_policy(reference_existing_object)

   fc.variables(allow_empty=True).exclude()
   finalize(fc)
   addFCPtrCode(fc,mb,"FieldContainer", None)   
   
   # For each node core class we have information about
   # - Generate for class and classBase
   for (key, cinfo) in core_info.core_infos.iteritems():
      base_name = None
      print "Core: %s"%cinfo.name
   
      # If requested, expose the base
      #if has_base:
      if cinfo.hasBase:
         try:
            base_name      = cinfo.name + "Base"
            base_c = osg.class_(base_name)
            base_c.include()
            finalize(base_c)
            handleClassDefaults(base_c)
         except Exception, ex:
            print "Exception when exposing base class [%s].  Skipping" % base_name
            print "  details: ", ex

         #removePureVirtuals(base_c)
         #for x in base_excludes:            
         #   base_c[x].exclude()
         #for (n,p) in base_policies:
         #   base_c[n].call_policies = p      
         
      # Expose the core class
      c = osg.class_(cinfo.name)
      c.include()
      finalize(c)      
      handleClassDefaults(c)
      #c.documentation = cinfo.documentation
      #removePureVirtuals(c)
      
      for x in cinfo.excludes:
         #print "   exclude:", x
	 try:
            c[x].exclude()
         except Exception, ex:
            pass
            #print "Exception excluding: %s\n%s"%(x,str(ex))
      for (n,p) in cinfo.policies:
         #print "   set policy for:", n
         try:
            c[n].call_policies = p   
         except Exception, ex:
            pass
            #print "Exception setting policies on: %s\n%s"%(n,str(ex))
      for method_info in cinfo.addMethods:         
         add_member_function(c, method_info[0],method_info[1])

      # Handle template naming. 
      # XXX: This is a hack for the one specific case it is used right now
      # it should be refactored to use some hints from cinfo to choose
      # naming method
      for mname in cinfo.templateMethods:
         print "  memeber: ", mname
         for f in c.member_functions(mname):
            ret_type = f.return_type
            print "    ret type: ", ret_type
            print "       dict: ", ret_type.__dict__
            if is_void(ret_type):
               f.alias = f.name
            elif is_fundamental(ret_type):
               ret_alias = f.name.replace(" ", "_")
               f.alias = "%s_%s"%(f.alias, ret_alias)               
            else:   # hasattr(ret_type, 'declaration'):
	       #type_def = getOsgTypedefFromType(ret_type)	       
               ret_alias = ret_type.declaration.alias
               f.alias = "%s_%s"%(f.alias, ret_alias)
               print "    aliases: %s  %s"%(f.alias, ret_alias)             
            f.name = f.demangled_name
            f.create_with_signature = True
	    
	    
      
      # Register all the pointer conversions that we need and sets held type
      addFCPtrCode(c, mb, cinfo.name, cinfo.parent)
      
      # Apply mods (modification methods)
      if core_info.core_mods.has_key(key):
         #print "Apply mods for: ", key
         for class_mod in core_info.core_mods[key][0]:
            class_mod(c)
         for base_mod in core_info.core_mods[key][1]:
            base_mod(base_c)

   # Customizations for field container classes.

   wrap_method(osg['Camera'], 'calcViewRay', 'osgwrap::calcViewRay')
   # The following ought to be the preferred way to handle transforming the
   # signature of OSG::Camera::calcViewRay(), but it does not work. This may
   # be because Py++ sees the const reference to an OSG::Viewport object, a
   # type that has a protected destructor, and sets up its own wrapper for
   # that method. In so doing, perhaps it is ignoring the transformation added
   # here. Or maybe this is just being done at the wrong point in this script.
#   calc_view_ray = osg['Camera']['calcViewRay']
#   calc_view_ray.add_transformation(FT.output(0))

   wrap_method(osg['Viewport'], 'getNormalizedCoordinates',
               'osgwrap::getNormalizedCoordinates')
   #vp = osg["Viewport"]
#   vp["getNormalizedCoordinates"].add_transformation(
#      FT.modify_type(0, declarations.remove_const), FT.output(0), FT.output(1),
#      alias = "getNormalizedCoordinates"
#   )

   # Do not hold the GIL while OSG::Window::render() is invoked. This will
   # allow other Python threads to run while the (relatively) slow rendering
   # process takes place.
   c = osg.class_("Window")
   c["render"].add_transformation(unlocked_native(), alias = "render")

   # Expose the derived geo prop classes (there are a *lot* of them)
   print "----- Geo Props -------"
   # Helper method
   def expose_geo_prop(temp, base_name):      
      c = temp.decl
      c.include()
      finalize(c)
      handleClassDefaults(c)
      addFCPtrCode(c, mb, c.name, base_name)   # Register the fcptr conversions

      # Exclude some methods that are difficult or impossible to wrap well
      for p in ["editFieldPtr","getFieldPtr","editField"]:
         c.decls(p,allow_empty=True).exclude()

      # Just use the base class version of these methods
      for n in ["getDimension","getNormalize","clear","resize","size","setValue","push_back","addValue"]:
	 c.decls(n,allow_empty=True).exclude()

   for temp in geo_vec_prop_templates:
      expose_geo_prop(temp, "OSG::GeoVectorProperty")
   for temp in geo_int_prop_templates:
      expose_geo_prop(temp, "OSG::GeoIntegralProperty")


   # Places that take an OSG::NullFC as a default value
   for x in ["createMipmap","scale","mirror", "reformat","scaleNextPower2","slice","subImage"]:
      osg["Image"][x].use_overload_macro = True   
   
   timing.append(["field containers",time.time()])
   
   # ------ Standard ------------- #
   # - Remove all op=
   # - Always expose with scope method
   all_classes = osg.classes()
   for c in all_classes:
      c.operators(symbol="=", allow_empty=True).exclude()
      c.always_expose_using_scope = True 

   timing.append(["standard",time.time()])
   
   print "Generating module..."
   
   #Now it is the time to give a name to our module
   mb.build_code_creator(module_name='_osg2')
   timing.append(["build creators",time.time()])
   
   mb.code_creator.add_namespace_usage("std")
   mb.code_creator.license = '// Licensed LGPL by Allen Bierbaum'
   
   #for h in extra_includes:
   #   mb.code_creator.add_include(h)
      
   # Replace the auto generated list of headers
   #mb.code_creator.replace_included_headers( all_headers + extra_includes)
   #mb.code_creator.replace_included_headers( [main_header_filename] + extra_includes)
   mb.code_creator.replace_included_headers( extra_includes)
   
   if False:
      decl_file = file("decl_out.txt",'w', 1024000)
      print "Writing out decls to file... ",
      mb.print_declarations(osg, writer=decl_file.write)
      #mb.print_declarations(my_class)
      decl_file.close()
      print "done."

   # Create a code creator for our precompiled header and ensure it comes first.
   precompiled_header = pyplusplus.code_creators.include_t(main_header_filename)
   mb.code_creator.adopt_creator( precompiled_header, 0 )

   # So we don't have absolute paths in code
   mb.code_creator.user_defined_directories.append(opensg_inc_dir)
   
   print "Splitting module..."
   mb.split_module(pj(output_dir,'generated'))
   timing.append(["write module",time.time()])
   

if use_profiler:
   import hotshot
   profiler = hotshot.Profile('gen_bindings.prof')
   profiler.runcall(main)
   profiler.close()
else:
   main()

print "Code generation complete.  took: %s"%(time.time()-timing[0][1])
for i in range(1,len(timing)):
   print "  %s: %s"%(timing[i][0], (timing[i][1]-timing[i-1][1]))

